This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / web / src / routes / [handle] / [repo] / +layout.ts
4.5 kB 122 lines
1import { error, redirect } from "@sveltejs/kit"; 2import { createBobbinClient } from "$lib/api/client"; 3import { count } from "$lib/api/count"; 4import { enrich, handleOf, TYPE_MINIDOC } from "$lib/api/enrich"; 5import { gitTarget, resolveDefaultBranch } from "$lib/api/gitclient"; 6import { getStarRkey } from "$lib/api/graph"; 7import { resolveMiniDoc } from "$lib/api/identity"; 8import { parallel, toHttpError } from "$lib/api/load"; 9import type { RecordView, RepoRecord } from "$lib/api/records"; 10import { repoNameOf, resolveRepoByName } from "$lib/api/repo"; 11import { didFromUri, rkeyFromUri } from "$lib/api/uri"; 12import type { BobbinContext } from "$lib/api/client"; 13import type { RepoCounts, RepoInfo, RepoSource } from "$lib/components/repo/types"; 14import type { LayoutLoad } from "./$types"; 15 16const FALLBACK_BRANCH = "main"; 17 18const resolveSource = async ( 19 ctx: BobbinContext, 20 uri: string | undefined 21): Promise<RepoSource | null> => { 22 if (!uri?.startsWith("at://")) return null; 23 try { 24 const page = await enrich<RecordView<RepoRecord>>(ctx, { 25 xrpc: "sh.tangled.repo.getRepo", 26 params: { repo: uri }, 27 enrich: [{ source: "sh.tangled.repo:.repo", type: TYPE_MINIDOC, targets: ["uri"] }] 28 }); 29 const view = page.output; 30 const ownerDid = didFromUri(view.uri); 31 return { 32 ownerHandle: handleOf(page.data, ownerDid, "sh.tangled.repo:.repo"), 33 name: repoNameOf(view) 34 }; 35 } catch { 36 // a fork whose source is gone still renders, just without the attribution 37 return null; 38 } 39}; 40 41// the layout reset also cuts us off from `[handle]/+layout.ts`, so identity gets 42// resolved again here 43export const load: LayoutLoad = async (event) => { 44 const parent = await event.parent(); 45 const identifier = decodeURIComponent(event.params.handle); 46 const name = decodeURIComponent(event.params.repo); 47 48 // rejects bare words so unrelated paths 404 instead of resolving as actors 49 if (!identifier.startsWith("did:") && !identifier.includes(".")) { 50 error(404, "Not found"); 51 } 52 53 const ctx = createBobbinClient({ serviceUrl: parent.publicConfig.bobbinUrl, fetch: event.fetch }); 54 const doc = await resolveMiniDoc(ctx, identifier).catch((cause) => 55 toHttpError(cause, "Could not resolve user") 56 ); 57 58 // redirects dids and stale handles to the canonical handle url 59 const canonical = doc.handle && !doc.handle.endsWith(".invalid") ? doc.handle : null; 60 if (canonical && identifier.toLowerCase() !== canonical.toLowerCase()) { 61 redirect(307, `/${canonical}/${event.params.repo}${event.url.search}`); 62 } 63 64 const view = await resolveRepoByName(ctx, doc.did, name).catch((cause) => 65 toHttpError(cause, "Could not load repository") 66 ); 67 if (!view) error(404, `${doc.handle}/${name} does not exist`); 68 69 const record = view.value; 70 const repoDid = record.repoDid; 71 const viewerDid = parent.auth?.did; 72 const git = gitTarget(parent.publicConfig, { uri: view.uri, repoDid }, event.fetch); 73 74 const stats = await parallel({ 75 // only the git backend knows the default branch, and one that is down 76 // or still syncing shouldn't take out the whole layout 77 defaultBranch: resolveDefaultBranch(git), 78 stars: repoDid 79 ? count(ctx, "sh.tangled.feed.countStars", repoDid).catch(() => null) 80 : Promise.resolve(null), 81 // the tabs count what is still open, closed ones matter on their own pages 82 issues: repoDid 83 ? count(ctx, "sh.tangled.repo.countIssues", repoDid, { state: "open" }).catch(() => null) 84 : Promise.resolve(null), 85 pulls: repoDid 86 ? count(ctx, "sh.tangled.repo.countPulls", repoDid, { status: "open" }).catch(() => null) 87 : Promise.resolve(null), 88 forks: repoDid 89 ? count(ctx, "sh.tangled.repo.countForks", repoDid).catch(() => null) 90 : Promise.resolve(null), 91 viewerStarRkey: 92 viewerDid && repoDid 93 ? getStarRkey(ctx, viewerDid, repoDid).catch(() => null) 94 : Promise.resolve(null), 95 source: resolveSource(ctx, record.source) 96 }); 97 98 const repo: RepoInfo = { 99 uri: view.uri, 100 rkey: rkeyFromUri(view.uri), 101 name: repoNameOf(view), 102 ownerDid: doc.did, 103 ownerHandle: doc.handle, 104 repoDid, 105 knot: record.knot, 106 spindle: record.spindle, 107 description: record.description, 108 website: record.website, 109 topics: record.topics, 110 source: stats.source ?? undefined, 111 defaultBranch: stats.defaultBranch ?? FALLBACK_BRANCH 112 }; 113 114 const counts: RepoCounts = { 115 stars: stats.stars?.count ?? 0, 116 issues: stats.issues?.count ?? 0, 117 pulls: stats.pulls?.count ?? 0, 118 forks: stats.forks?.count ?? 0 119 }; 120 121 return { repo, counts, viewerStarRkey: stats.viewerStarRkey }; 122};